/*
 * File      : sz.c
 * the implemention of sending files to the remote computers
 * through the zmodem protocol.
 * Change Logs:
 * Date           Author       Notes
 * 2011-03-29     itspy
 */

#include <rtthread.h>
#include <finsh.h>
#include <shell.h>
#include <rtdef.h>
#include <dfs.h>
#include <dfs_file.h>
#include <dfs_posix.h>
#include "zdef.h"


static rt_uint8_t TX_BUFFER[TX_BUFFER_SIZE];	     /* sender buffer */
static rt_uint8_t file_cnt = 0;		                 /* count of number of files opened */
static rt_uint8_t Rxflags  = 0;	                  	 /* rx parameter flags */
static rt_uint8_t ZF2_OP;		                  	 /* file transfer option */

void zs_start(char* path);
static void zsend_init(void);
static rt_err_t zsend_files(struct zfile* zf);
static rt_err_t zsend_file(struct zfile* zf, rt_uint8_t* buf, rt_uint16_t len);
static rt_err_t zsend_file_data(struct zfile* zf);
static rt_uint16_t zfill_buffer(struct zfile* zf, rt_uint8_t* buf, rt_uint16_t size);
static rt_err_t zget_sync(void);
static void zsay_bibi(void);




/* start zmodem send process */
void zs_start(char* path)
{
	struct zfile* zf;
	rt_err_t res = RT_ERROR;
	char* p, *q;
	zf = rt_malloc(sizeof(struct zfile));

	if(zf == RT_NULL) {
		rt_kprintf("zf: out of memory\r\n");
		return;
	}

	rt_kprintf("\r\nsz: ready...\r\n");	   /* here ready to send things */
	memset(zf, 0, sizeof(struct zfile));
	zf->fname = path;
	zf->fd = -1;
	res = zsend_files(zf);
	p = zf->fname;

	for(;;) {
		q = strstr(p, "/");

		if(q == RT_NULL)  break;

		p = q + 1;
	}

	if(res == RT_EOK) {
		rt_kprintf("\r\nfile: %s \r\nsize: %ld bytes\r\nsend completed.\r\n",
		           p, zf->bytes_received);
	} else {
		rt_kprintf("\r\nfile: %s \r\nsize: 0 bytes\r\nsend failed.\r\n", p);
	}

	rt_free(zf);

	return;
}

/* init the parameters */
static void zsend_init(void)
{
	rt_err_t res = -RT_ERROR;

	zinit_parameter();

	for(;;) {        /* wait ZPAD */
		res = zread_line(800);

		if(res == ZPAD) break;
	}

	for(;;) {
		res = zget_header(rx_header);

		if(res == ZRINIT) break;
	}

	if((rx_header[ZF1] & ZRQNVH)) {
		zput_pos(0x80L);	/* Show we can var header */
		zsend_hex_header(ZRQINIT, tx_header);
	}

	Rxflags = rx_header[ZF0] & 0377;

	if(Rxflags & CANFC32) Txfcs32 = 1;     /* used 32bits CRC check */

	if(ZF2_OP == ZTRLE && (Rxflags & CANRLE))	   /* for RLE packet */
		Txfcs32 = 2;
	else
		ZF2_OP = 0;

	/* send SINIT cmd */
	return;
}

/* send files */
static rt_err_t zsend_files(struct zfile* zf)
{
	char* p, *q;
	char* str = "/";
	struct stat finfo;
	rt_err_t res = -RT_ERROR;

	if(zf->fname == RT_NULL) {
		rt_kprintf("\r\nerror: no file to be send.\r\n");
		return res;
	}

	if((zf->fd = open(zf->fname, DFS_O_RDONLY, 0)) < 0) {
		rt_kprintf("\r\ncan not open file:%s\r\n", zf->fname + 1);
		return res;
	}

	zf->file_end = 0;
	++file_cnt;
	/* extract file name */
	p = zf->fname;

	for(;;) {
		q = strstr(p, str);

		if(q == RT_NULL)  break;

		p = q + 1;
	}

	q = (char*)TX_BUFFER;

	for(;;) {
		*q++ = *p++;

		if(*p == 0) break;
	}

	*q++ = 0;
	p = q;

	while(q < (char*)(TX_BUFFER + 1024))
		*q++ = 0;

	/* get file attributes */
	fstat(zf->fd, &finfo);
	Left_sizes += finfo.st_size;
	rt_sprintf(p, "%lu %lo %o 3 %d %ld", (long)finfo.st_size, finfo.st_mtime,
	           finfo.st_mode, file_cnt, Left_sizes);
	Left_sizes -= finfo.st_size;
	TX_BUFFER[127] = (finfo.st_size + 127) >> 7;
	TX_BUFFER[126] = (finfo.st_size + 127) >> 15;

	zsend_init();
	/* start sending files */
	res = zsend_file(zf, TX_BUFFER, (p - (char*)TX_BUFFER) + strlen(p) + 1);
	zsay_bibi();
	close(zf->fd);

	return res;
}

/* send file name and related info */
static rt_err_t zsend_file(struct zfile* zf, rt_uint8_t* buf, rt_uint16_t len)
{
	rt_uint8_t cnt;
	rt_err_t res = -RT_ERROR;

	for(cnt = 0; cnt < 5; cnt++) {
		tx_header[ZF0] = ZF0_CMD;	            /* file conversion option */
		tx_header[ZF1] = ZF1_CMD;               /* file management option */
		tx_header[ZF2] = (ZF3_CMD | ZF2_OP);	  /* file transfer option   */
		tx_header[ZF3] = ZF3_CMD;
		zsend_bin_header(ZFILE, tx_header);
		zsend_bin_data(buf, len, ZCRCW);
loop:
		res = zget_header(rx_header);

		switch(res) {
			case ZRINIT:
				while((res = zread_line(50)) > 0) {
					if(res == ZPAD) {
						goto loop;
					}
				}

				break;

			case ZCAN:
			case TIMEOUT:
			case ZABORT:
			case ZFIN:
				break;

			case -RT_ERROR:
			case ZNAK:
				break;

			case ZCRC:	                         /* no CRC request */
				goto loop;

			case ZFERR:
			case ZSKIP:
				break;

			case ZRPOS:		           	         /* here we want */
				zget_pos(Rxpos);
				Txpos = Rxpos;
				return(zsend_file_data(zf));

			default:
				break;
		}
	}

	return res;
}

/* send the file data */
static rt_err_t zsend_file_data(struct zfile* zf)
{
	rt_int16_t cnt;
	rt_uint8_t cmd;
	rt_err_t res = -RT_ERROR;
	/* send ZDATA packet, start to send data */
start_send:
	zput_pos(Txpos);
	zsend_bin_header(ZDATA, tx_header);

	do {
		cnt = zfill_buffer(zf, TX_BUFFER, RX_BUFFER_SIZE);

		if(cnt < RX_BUFFER_SIZE)
			cmd = ZCRCE;
		else
			cmd = ZCRCG;

		zsend_bin_data(TX_BUFFER, cnt, cmd);
		zf->bytes_received = Txpos += cnt;

		if(cmd == ZCRCW)
			goto get_syn1;
	} while(cnt == RX_BUFFER_SIZE);

	for(;;) {                      /*  get ack and check if send finish */
		zput_pos(Txpos);
		zsend_bin_header(ZEOF, tx_header);
get_syn1:
		res = zget_sync();

		switch(res) {
			case ZACK:
				goto get_syn1;

			case ZNAK:
				continue;

			case ZRPOS:				        /* resend here */
				lseek(zf->fd, Txpos, 0);
				goto start_send;

			case ZRINIT:		           /*  send finish,then begin to send next file */
				return RT_EOK;

			case ZSKIP:
			case -RT_ERROR:
				return res;

			default:
				return res;
		}
	}
}

/* fill file data to buffer*/
static rt_uint16_t zfill_buffer(struct zfile* zf, rt_uint8_t* buf, rt_uint16_t size)
{
	return (read(zf->fd, buf, size));
}

/* wait sync(ack) from the receiver */
static rt_err_t zget_sync(void)
{
	rt_err_t res = -RT_ERROR;

	for(;;) {
		res = zget_header(rx_header);

		switch(res) {
			case ZCAN:
			case ZABORT:
			case ZFIN:
			case TIMEOUT:
				return -RT_ERROR;

			case ZRPOS:		             /* get pos, need to resend */
				zget_pos(Rxpos);
				Txpos = Rxpos;
				return res;

			case ZACK:
				return res;

			case ZRINIT:		         /* get ZRINIT indicate that the prev file send completed */
				return res;

			case ZSKIP:
				return res;

			case -RT_ERROR:
			default:
				zsend_bin_header(ZNAK, tx_header);
				continue;
		}
	}
}

/* say "bibi" to the receiver */
static void zsay_bibi(void)
{
	for(;;) {
		zput_pos(0L);	            	      /* reninit position of next file*/
		zsend_hex_header(ZFIN, tx_header);	  /* send finished session cmd */

		switch(zget_header(rx_header)) {
			case ZFIN:
				zsend_line('O');
				zsend_line('O');

			case ZCAN:
			case TIMEOUT:
				return;
		}
	}
}
/* end of sz.c */
